/*
 * Copyright (c) 2012 Universidad Antonio de Nebrija.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Apache License v2.0
 * which accompanies this distribution, and is available at
 * http://www.apache.org/licenses/LICENSE-2.0.html
 * 
 * Contributors:
 *    Universidad Antonio de Nebrija - initial API, implementation and documentation
 */
package com.nebrija.riif.model;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

/**
 * Class implementing the model for the RIIF component token
 * @author asanchep
 *
 */
public class Component implements RIIFElement, RIIFPropertiesContainer{

	/** name of the component*/
	protected String name = "";

	/** Namespace for all the elements included in this component*/
	private HashMap<String,RIIFElement> elements = new HashMap<String, RIIFElement>();
	/** Namespace for all the child components included in this component*/
	private HashMap<String,ChildComponent> childComponents = new HashMap<String,ChildComponent>();
	/** Namespace for all the parameters included in this component*/
	private HashMap<String,Parameter> parameters = new HashMap<String,Parameter>();
	/** Namespace for all the fail modes included in this component*/
	private HashMap<String,FailMode> failModes = new HashMap<String,FailMode>();
	/** Namespace for all the constants included in this component*/
	private HashMap<String, Constant> constants = new HashMap<String, Constant>();
	
	/** Component it derives from */
	private Component parent = null;
	
	public Component(){
	}
	
	public Component(String name){
		this.name=name;
	}
	
	
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	/**
	 * Function that adds a child component to this component. It is added to the child component and element lists
	 * @param c The child component to be added
	 * @return an error string or an empty string if no error
	 */
	public String addChildComponent(ChildComponent c){
		if (c==null){
			return "Null component";
		}
		if (c.getName()==null || c.getName().equals("")){
			return "Null component";
		}
		if (existElement(c.getName())){
			return "An element with that name already exists in this component"; // TODO Internationalization
		}
		childComponents.put(c.getName(), c);
		elements.put(c.getName(), c);
		return "";
	}

	/**
	 * Check if a child component exists
	 * @param c child component
	 * @return true if it exists, false otherwise
	 */
	public boolean hasChildComponent(ChildComponent c){
		if (childComponents.get(c.getName())!=null){
			return true;
		}
		if(parent!=null){
			return parent.hasChildComponent(c);
		}
		return false;
	}

	/**
	 * Function that adds a fail mode to this component. It is added to the fail mode and element lists
	 * @param fm the fail mode to be added
	 * @return an error string or an empty string if no error
	 */
	public String addFailMode(FailMode fm){
		if (fm==null){
			return "Null";
		}
		if (fm.getName()==null || fm.getName().equals("")){
			return "Null";
		}
		if (existElement(fm.getName())){
			return "An element with that name already exists in this component"; // TODO Internationalization
		}
		failModes.put(fm.getName(), fm);
		elements.put(fm.getName(), fm);
		return "";
	}	
	
	/**
	 * Check if a fail mode exists
	 * @param fm fail mode
	 * @return true if it exists, false otherwise
	 */	
	public boolean hasFailMode(FailMode fm){
		if (failModes.get(fm.getName())!=null){
			return true;
		}
		if(parent!=null){
			return parent.hasFailMode(fm);
		}
		return false;
	} 
	
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder(getName());
		
		if (parent!=null){
			sb.append(" extends ").append(parent.getName());
		}
		
		sb.append("\n->PARAMETERS: \n");
		Set<String> paramNames = parameters.keySet();
		for(String name: paramNames){
			sb.append("\t").append(parameters.get(name).toString());
		}
		
		sb.append("\n->FAIL MODES: \n");
		Set<String> failNames = failModes.keySet();
		for(String name: failNames){
			sb.append("\t").append(failModes.get(name).toString());
		}
		
		sb.append("\n->CONSTANTS: \n");
		Set<String> constantNames = constants.keySet();
		for(String name: constantNames){
			sb.append("\t").append(constants.get(name).toString());
		}
	
		sb.append("\n->CHILD_COMPONENTS: \n");
		Set<String> childNames = childComponents.keySet();
		for(String name: childNames){
			sb.append("\t").append(childComponents.get(name).toString());
		}		
		
		return sb.toString();
	}

	/**
	 * Function that adds a constant to this component. It is added to the constant and element lists
	 * @param co the constant to be added
	 * @return an error string or an empty string if no error
	 */
	public String addConstant(Constant co) {
		if (co==null){
			return "Null";
		}
		if (co.getName()==null || co.getName().equals("")){
			return "Null";
		}
		if (existElement(co.getName())){
			return "An element with that name already exists in this component"; // TODO Internationalization
		}
		constants.put(co.getName(), co);
		elements.put(co.getName(), co);
		return "";
	}
	
	/**
	 * Check if a constant exists
	 * @param co constant
	 * @return true if it exists, false otherwise
	 */	
	public boolean hasConstant(Constant co) {
		if (constants.get(co.getName())!=null){
			return true;
		}
		if(parent!=null){
			return parent.hasConstant(co);
		}
		return false;
	}
	
	/**
	 * Check if an element exists
	 * @param name the element name
	 * @return true if it exists, false otherwise
	 */	
	public boolean existElement(String name){
		if (elements.get(name)!=null){
			return true;
		}
		if(parent!=null){
			return parent.existElement(name);
		}
		return false;
	}
	
	/**
	 * Return an element if it is a RIIFProperties object (parameter, constant, fail_mode)
	 * @param name the element name
	 * @return the object if it exists and it is a RIIFProperties object, null otherwise
	 */
	public RIIFProperties getPropertiesElement(String name){
		RIIFElement element = (RIIFElement) elements.get(name);
		if (element==null && parent!=null){
			element = parent.getPropertiesElement(name);
		}
		if (element==null || !(element instanceof RIIFProperties)){
			return null;
		}
		return (RIIFProperties)element;
		
	}

	/**
	 * Function that adds a parameter to this component. It is added to the parameter and element lists
	 * @param par the parameter to be added
	 * @return an error string or an empty string if no error
	 */
	public String addParameter(Parameter par){
		if (par==null){
			return "Null";
		}
		if (par.getName()==null || par.getName().equals("")){
			return "Null";
		}
		if (existElement(par.getName())){
			return "An element with that name already exists in this component"; // TODO Internationalization
		}
		parameters.put(par.getName(), par);
		elements.put(par.getName(), par);
		return "";
	}
	
	/**
	 * Returns the type (constant, parameter, fail_mode, child_component, none) of the element requested
	 * @param name the name of the element
	 * @return the type of the element or NONE if the element is not found or the type is unknown
	 */
	public RIIFComponentPartType getType(String name){
		if(!existElement(name)){
			return RIIFComponentPartType.NONE;
		}
		if (getConstant(name)!=null){
			return RIIFComponentPartType.CONSTANT;
		}
		if (getParameter(name)!=null){
			return RIIFComponentPartType.PARAMETER;
		}
		if (getFailMode(name)!=null){
			return RIIFComponentPartType.FAIL_MODE;
		}
		if (getChildComponent(name)!=null){
			return RIIFComponentPartType.CHILD_COMPONENT;
		}
		return RIIFComponentPartType.NONE;
	}
	
	/**
	 * Function that adds an element to this component. It is added to the specific list and element lists
	 * @param fm the fail mode to be added
	 * @return an error string or an empty string if no error
	 */
	public void addElement (RIIFElement element, RIIFComponentPartType type){
		switch(type){
			case CONSTANT:
				this.addConstant((Constant)element);
				break;
			case PARAMETER:
				this.addParameter((Parameter)element);
				break;
			case FAIL_MODE:
				this.addFailMode((FailMode)element);
				break;
			case CHILD_COMPONENT:
				this.addChildComponent((ChildComponent)element);
				break;				
			case NONE:
		}
	}
	
	/**
	 * Returns a child component given its name
	 * @param name the child component's name
	 * @return the object if it exists or null otherwise
	 */
	public ChildComponent getChildComponent(String name){
		ChildComponent comp = childComponents.get(name);
		if (comp==null && parent!=null){
			comp = parent.getChildComponent(name);
		}
		return comp;
	}
	
	/**
	 * Returns a constant given its name
	 * @param name the constant's name
	 * @return the object if it exists or null otherwise
	 */	
	public Constant getConstant(String name){
		Constant co = constants.get(name);
		if (co==null && parent!=null){
			co = parent.getConstant(name);
		}
		return co;
	}

	
	/**
	 * Returns a parameter given its name
	 * @param name the parameter's name
	 * @return the object if it exists or null otherwise
	 */	
	public Parameter getParameter(String name){
		Parameter par = parameters.get(name);
		if (par==null && parent!=null){
			par = parent.getParameter(name);
		}
		return par;
	}

	/**
	 * Returns a fail mode given its name
	 * @param name the failmode's name
	 * @return the object if it exists or null otherwise
	 */	
	public FailMode getFailMode(String name){
		FailMode fm = failModes.get(name);
		if (fm==null && parent!=null){
			fm = parent.getFailMode(name);
		}
		return fm;
	}	
	
	/**
	 * Returns an element (constant, parameter, child component, fail mode...) given its name
	 * @param name the name of the element
	 * @return the object if it exists or null otherwise
	 */	
	public RIIFElement getElement(String name){
		RIIFElement ele = elements.get(name);
		if (ele==null && parent!=null){
			ele = parent.getElement(name);
		}
		return ele;
	}	

	/**
	 * Getter for the elements included in this component
	 * @return a hashmap with the elements indexed by name
	 */
	public HashMap<String,RIIFElement> getElements(){
		return elements;
	}
	
	@Override
	public void resolve(RIIFElement container) {
		//propagate to its parent
		if(parent!=null){
			parent.resolve(container);
		}
		//propagate to its elements
		for (RIIFElement element: elements.values()){
			element.resolve(this);
		}
		
	}	
	
	@Override
	public void clear(RIIFElement container) {
		//propagate to its parent
		if(parent!=null){
			parent.clear(container);
		}
		//propagate to its elements
		for (RIIFElement element: elements.values()){
			element.clear(this);
		}
		
	}

	public Component getParent() {
		return parent;
	}

	public void setParent(Component parent) {
		this.parent = parent;
	}

	@Override
	public int getChildCount() {
		int size = elements.size();
		if(parent!=null){
			size+=parent.getChildCount();
		}
		return size;
	}

	@Override
	public Set<String> getElementNames() {
		Set<String> keys = new HashSet<String>();
		keys.addAll(elements.keySet());
		if(parent!=null){
			keys.addAll(parent.getElementNames());
		}
		return keys;
	}

	
}
